/*
 * D-Bus Java Implementation Copyright (c) 2005-2006 Matthew Johnson This
 * program is free software; you can redistribute it and/or modify it under the
 * terms of either the GNU Lesser General Public License Version 2 or the
 * Academic Free Licence Version 2.1. Full licence texts are included in the
 * COPYING file with this program.
 */
package org.freedesktop.dbus;

import static org.freedesktop.dbus.Gettext._;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.freedesktop.dbus.exceptions.DBusException;
import org.freedesktop.dbus.exceptions.MarshallingException;
import org.freedesktop.dbus.exceptions.UnknownTypeCodeException;

/**
 * Superclass of all messages which are sent over the Bus. This class deals with
 * all the marshalling to/from the wire format.
 */
public class Message {
  /**
   * Defines constants for each argument type. There are two constants for each
   * argument type, as a byte or as a String (the _STRING version)
   */
  public static interface ArgumentType {
    public static final byte   ARRAY              = 'a';
    public static final String ARRAY_STRING       = "a";
    public static final byte   BOOLEAN            = 'b';
    public static final String BOOLEAN_STRING     = "b";
    public static final byte   BYTE               = 'y';
    public static final String BYTE_STRING        = "y";
    public static final byte   DICT_ENTRY         = 'e';
    public static final String DICT_ENTRY_STRING  = "e";
    public static final byte   DICT_ENTRY1        = '{';
    public static final String DICT_ENTRY1_STRING = "{";
    public static final byte   DICT_ENTRY2        = '}';
    public static final String DICT_ENTRY2_STRING = "}";
    public static final byte   DOUBLE             = 'd';
    public static final String DOUBLE_STRING      = "d";
    public static final byte   FLOAT              = 'f';
    public static final String FLOAT_STRING       = "f";
    public static final byte   INT16              = 'n';
    public static final String INT16_STRING       = "n";
    public static final byte   INT32              = 'i';
    public static final String INT32_STRING       = "i";
    public static final byte   INT64              = 'x';

    public static final String INT64_STRING       = "x";
    public static final byte   OBJECT_PATH        = 'o';
    public static final String OBJECT_PATH_STRING = "o";
    public static final byte   SIGNATURE          = 'g';
    public static final String SIGNATURE_STRING   = "g";
    public static final byte   STRING             = 's';
    public static final String STRING_STRING      = "s";
    public static final byte   STRUCT             = 'r';
    public static final String STRUCT_STRING      = "r";
    public static final byte   STRUCT1            = '(';
    public static final String STRUCT1_STRING     = "(";
    public static final byte   STRUCT2            = ')';
    public static final String STRUCT2_STRING     = ")";
    public static final byte   UINT16             = 'q';
    public static final String UINT16_STRING      = "q";
    public static final byte   UINT32             = 'u';
    public static final String UINT32_STRING      = "u";
    public static final byte   UINT64             = 't';
    public static final String UINT64_STRING      = "t";
    public static final byte   VARIANT            = 'v';
    public static final String VARIANT_STRING     = "v";
  }

  /** Defines constants representing the endianness of the message. */
  public static interface Endian {
    public static final byte BIG    = 'B';
    public static final byte LITTLE = 'l';
  }

  /** Defines constants representing the flags which can be set on a message. */
  public static interface Flags {
    public static final byte ASYNC             = 0x40;
    public static final byte NO_AUTO_START     = 0x02;
    public static final byte NO_REPLY_EXPECTED = 0x01;
  }

  /** Defines constants for each valid header field type. */
  public static interface HeaderField {
    public static final byte DESTINATION  = 6;
    public static final byte ERROR_NAME   = 4;
    public static final byte INTERFACE    = 2;
    public static final byte MEMBER       = 3;
    public static final byte PATH         = 1;
    public static final byte REPLY_SERIAL = 5;
    public static final byte SENDER       = 7;
    public static final byte SIGNATURE    = 8;
  }

  /** Defines constants for each message type. */
  public static interface MessageType {
    public static final byte ERROR         = 3;
    public static final byte METHOD_CALL   = 1;
    public static final byte METHOD_RETURN = 2;
    public static final byte SIGNAL        = 4;
  }

  /** Steps to increment the buffer array. */
  private static final int BUFFERINCREMENT = 20;
  protected static long    globalserial    = 0;
  /**
   * Keep a static reference to each size of padding array to prevent
   * allocation.
   */
  private static byte[][]  padding;
  /** The current protocol major version. */
  public static final byte PROTOCOL        = 1;

  static {
    padding = new byte[][] { null, new byte[1], new byte[2], new byte[3], new byte[4], new byte[5], new byte[6], new byte[7] };
  }

  /**
   * Demarshalls an integer of a given width from a buffer.
   * 
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          The offset to demarshall from.
   * @param endian
   *          The endianness to use in demarshalling.
   * @param width
   *          The byte-width of the int.
   */
  public static long demarshallint(byte[] buf, int ofs, byte endian, int width) {
    return endian == Endian.BIG ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
  }

  /**
   * Demarshalls an integer of a given width from a buffer using big-endian
   * format.
   * 
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          The offset to demarshall from.
   * @param width
   *          The byte-width of the int.
   */
  public static long demarshallintBig(byte[] buf, int ofs, int width) {
    long l = 0;
    for (int i = 0; i < width; i++) {
      l <<= 8;
      l |= (buf[ofs + i] & 0xFF);
    }
    return l;
  }

  /**
   * Demarshalls an integer of a given width from a buffer using little-endian
   * format.
   * 
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          The offset to demarshall from.
   * @param width
   *          The byte-width of the int.
   */
  public static long demarshallintLittle(byte[] buf, int ofs, int width) {
    long l = 0;
    for (int i = (width - 1); i >= 0; i--) {
      l <<= 8;
      l |= (buf[ofs + i] & 0xFF);
    }
    return l;
  }

  /**
   * Return the alignment for a given type.
   */
  public static int getAlignment(byte type) {
    switch (type) {
      case 2:
      case ArgumentType.INT16:
      case ArgumentType.UINT16:
        return 2;
      case 4:
      case ArgumentType.BOOLEAN:
      case ArgumentType.FLOAT:
      case ArgumentType.INT32:
      case ArgumentType.UINT32:
      case ArgumentType.STRING:
      case ArgumentType.OBJECT_PATH:
      case ArgumentType.ARRAY:
        return 4;
      case 8:
      case ArgumentType.INT64:
      case ArgumentType.UINT64:
      case ArgumentType.DOUBLE:
      case ArgumentType.STRUCT:
      case ArgumentType.DICT_ENTRY:
      case ArgumentType.STRUCT1:
      case ArgumentType.DICT_ENTRY1:
      case ArgumentType.STRUCT2:
      case ArgumentType.DICT_ENTRY2:
        return 8;
      case 1:
      case ArgumentType.BYTE:
      case ArgumentType.SIGNATURE:
      case ArgumentType.VARIANT:
      default:
        return 1;
    }
  }

  /**
   * Returns the name of the given header field.
   */
  public static String getHeaderFieldName(byte field) {
    switch (field) {
      case HeaderField.PATH:
        return "Path";
      case HeaderField.INTERFACE:
        return "Interface";
      case HeaderField.MEMBER:
        return "Member";
      case HeaderField.ERROR_NAME:
        return "Error Name";
      case HeaderField.REPLY_SERIAL:
        return "Reply Serial";
      case HeaderField.DESTINATION:
        return "Destination";
      case HeaderField.SENDER:
        return "Sender";
      case HeaderField.SIGNATURE:
        return "Signature";
      default:
        return "Invalid";
    }
  }

  /**
   * Marshalls an integer of a given width into a buffer using big-endian
   * format.
   * 
   * @param l
   *          The integer to marshall.
   * @param buf
   *          The buffer to marshall to.
   * @param ofs
   *          The offset to marshall to.
   * @param width
   *          The byte-width of the int.
   */
  public static void marshallintBig(long l, byte[] buf, int ofs, int width) {
    for (int i = (width - 1); i >= 0; i--) {
      buf[i + ofs] = (byte) (l & 0xFF);
      l >>= 8;
    }
  }

  /**
   * Marshalls an integer of a given width into a buffer using little-endian
   * format.
   * 
   * @param l
   *          The integer to marshall.
   * @param buf
   *          The buffer to demarshall to.
   * @param ofs
   *          The offset to demarshall to.
   * @param width
   *          The byte-width of the int.
   */
  public static void marshallintLittle(long l, byte[] buf, int ofs, int width) {
    for (int i = 0; i < width; i++) {
      buf[i + ofs] = (byte) (l & 0xFF);
      l >>= 8;
    }
  }

  private Object[]            args;
  private boolean             big;
  private byte[]              body;
  private long                bodylen      = 0;
  private int                 bufferuse    = 0;
  protected long              bytecounter;
  protected byte              flags;
  protected Map<Byte, Object> headers;

  private byte[]              pabuf;

  private int                 paofs        = 0;
  private int                 preallocated = 0;
  protected byte              protover;
  protected long              serial;
  protected byte              type;
  protected byte[][]          wiredata;

  /**
   * Create a blank message. Only to be used when calling populate.
   */
  protected Message() {
    wiredata = new byte[BUFFERINCREMENT][];
    headers = new HashMap<Byte, Object>();
    bytecounter = 0;
  }

  /**
   * Create a message; only to be called by sub-classes.
   * 
   * @param endian
   *          The endianness to create the message.
   * @param type
   *          The message type.
   * @param flags
   *          Any message flags.
   */
  protected Message(byte endian, byte type, byte flags) throws DBusException {
    wiredata = new byte[BUFFERINCREMENT][];
    headers = new HashMap<Byte, Object>();
    big = (Endian.BIG == endian);
    bytecounter = 0;
    synchronized (Message.class) {
      serial = ++globalserial;
    }
    this.type = type;
    this.flags = flags;
    preallocate(4);
    append("yyyy", endian, type, flags, Message.PROTOCOL);
  }

  /**
   * Align a counter to the given type.
   * 
   * @param current
   *          The current counter.
   * @param type
   *          The type to align to.
   * @return The new, aligned, counter.
   */
  public int align(int current, byte type) {
    int a = getAlignment(type);
    if (0 == (current % a)) { return current; }
    return current + (a - (current % a));
  }

  /**
   * Append a series of values to the message.
   * 
   * @param sig
   *          The signature(s) of the value(s).
   * @param data
   *          The value(s).
   */
  public void append(String sig, Object... data) throws DBusException {
    byte[] sigb = sig.getBytes();
    int j = 0;
    for (int i = 0; i < sigb.length; i++) {
      i = appendone(sigb, i, data[j++]);
    }
  }

  /**
   * Appends a byte to the buffer list.
   */
  protected void appendByte(byte b) {
    if (preallocated > 0) {
      pabuf[paofs++] = b;
      preallocated--;
    } else {
      if (bufferuse == wiredata.length) {
        byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
        System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
        wiredata = temp;
      }
      wiredata[bufferuse++] = new byte[] { b };
      bytecounter++;
    }
  }

  /**
   * Appends a buffer to the buffer list.
   */
  protected void appendBytes(byte[] buf) {
    if (null == buf) { return; }
    if (preallocated > 0) {
      if (paofs + buf.length > pabuf.length) { throw new ArrayIndexOutOfBoundsException(MessageFormat.format(
          _("Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}."), new Object[] { paofs, pabuf.length,
              buf.length })); }
      System.arraycopy(buf, 0, pabuf, paofs, buf.length);
      paofs += buf.length;
      preallocated -= buf.length;
    } else {
      if (bufferuse == wiredata.length) {
        byte[][] temp = new byte[wiredata.length + BUFFERINCREMENT][];
        System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
        wiredata = temp;
      }
      wiredata[bufferuse++] = buf;
      bytecounter += buf.length;
    }
  }

  /**
   * Marshalls an integer of a given width and appends it to the message.
   * Endianness is determined from the message.
   * 
   * @param l
   *          The integer to marshall.
   * @param width
   *          The byte-width of the int.
   */
  public void appendint(long l, int width) {
    byte[] buf = new byte[width];
    marshallint(l, buf, 0, width);
    appendBytes(buf);
  }

  /**
   * Appends a value to the message. The type of the value is read from a D-Bus
   * signature and used to marshall the value.
   * 
   * @param sigb
   *          A buffer of the D-Bus signature.
   * @param sigofs
   *          The offset into the signature corresponding to this value.
   * @param data
   *          The value to marshall.
   * @return The offset into the signature of the end of this value's type.
   */
  @SuppressWarnings("unchecked")
  private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException {
    try {
      int i = sigofs;

      // pad to the alignment of this type.
      pad(sigb[i]);
      switch (sigb[i]) {
        case ArgumentType.BYTE:
          appendByte(((Number) data).byteValue());
          break;
        case ArgumentType.BOOLEAN:
          appendint(((Boolean) data).booleanValue() ? 1 : 0, 4);
          break;
        case ArgumentType.DOUBLE:
          long l = Double.doubleToLongBits(((Number) data).doubleValue());
          appendint(l, 8);
          break;
        case ArgumentType.FLOAT:
          int rf = Float.floatToIntBits(((Number) data).floatValue());
          appendint(rf, 4);
          break;
        case ArgumentType.UINT32:
          appendint(((Number) data).longValue(), 4);
          break;
        case ArgumentType.INT64:
          appendint(((Number) data).longValue(), 8);
          break;
        case ArgumentType.UINT64:
          if (big) {
            appendint(((UInt64) data).top(), 4);
            appendint(((UInt64) data).bottom(), 4);
          } else {
            appendint(((UInt64) data).bottom(), 4);
            appendint(((UInt64) data).top(), 4);
          }
          break;
        case ArgumentType.INT32:
          appendint(((Number) data).intValue(), 4);
          break;
        case ArgumentType.UINT16:
          appendint(((Number) data).intValue(), 2);
          break;
        case ArgumentType.INT16:
          appendint(((Number) data).shortValue(), 2);
          break;
        case ArgumentType.STRING:
        case ArgumentType.OBJECT_PATH:
          // Strings are marshalled as a UInt32 with the length,
          // followed by the String, followed by a null byte.
          String payload = data.toString();
          byte[] payloadbytes = null;
          try {
            payloadbytes = payload.getBytes("UTF-8");
          } catch (UnsupportedEncodingException UEe) {
            throw new DBusException(_("System does not support UTF-8 encoding"));
          }
          appendint(payloadbytes.length, 4);
          appendBytes(payloadbytes);
          appendBytes(padding[1]);
          // pad(ArgumentType.STRING);? do we need this?
          break;
        case ArgumentType.SIGNATURE:
          // Signatures are marshalled as a byte with the length,
          // followed by the String, followed by a null byte.
          // Signatures are generally short, so preallocate the array
          // for the string, length and null byte.
          if (data instanceof Type[]) {
            payload = Marshalling.getDBusType((Type[]) data);
          } else {
            payload = (String) data;
          }
          byte[] pbytes = payload.getBytes();
          preallocate(2 + pbytes.length);
          appendByte((byte) pbytes.length);
          appendBytes(pbytes);
          appendByte((byte) 0);
          break;
        case ArgumentType.ARRAY:
          // Arrays are given as a UInt32 for the length in bytes,
          // padding to the element alignment, then elements in
          // order. The length is the length from the end of the
          // initial padding to the end of the last element.

          byte[] alen = new byte[4];
          appendBytes(alen);
          pad(sigb[++i]);
          long c = bytecounter;

          // optimise primatives
          if (data.getClass().isArray() && data.getClass().getComponentType().isPrimitive()) {
            byte[] primbuf;
            int algn = getAlignment(sigb[i]);
            int len = Array.getLength(data);
            switch (sigb[i]) {
              case ArgumentType.BYTE:
                primbuf = (byte[]) data;
                break;
              case ArgumentType.INT16:
              case ArgumentType.INT32:
              case ArgumentType.INT64:
                primbuf = new byte[len * algn];
                for (int j = 0, k = 0; j < len; j++, k += algn) {
                  marshallint(Array.getLong(data, j), primbuf, k, algn);
                }
                break;
              case ArgumentType.BOOLEAN:
                primbuf = new byte[len * algn];
                for (int j = 0, k = 0; j < len; j++, k += algn) {
                  marshallint(Array.getBoolean(data, j) ? 1 : 0, primbuf, k, algn);
                }
                break;
              case ArgumentType.DOUBLE:
                primbuf = new byte[len * algn];
                if (data instanceof float[]) {
                  for (int j = 0, k = 0; j < len; j++, k += algn) {
                    marshallint(Double.doubleToRawLongBits(((float[]) data)[j]), primbuf, k, algn);
                  }
                } else {
                  for (int j = 0, k = 0; j < len; j++, k += algn) {
                    marshallint(Double.doubleToRawLongBits(((double[]) data)[j]), primbuf, k, algn);
                  }
                }
                break;
              case ArgumentType.FLOAT:
                primbuf = new byte[len * algn];
                for (int j = 0, k = 0; j < len; j++, k += algn) {
                  marshallint(Float.floatToRawIntBits(((float[]) data)[j]), primbuf, k, algn);
                }
                break;
              default:
                throw new MarshallingException(_("Primative array being sent as non-primative array."));
            }
            appendBytes(primbuf);
          } else if (data instanceof List) {
            Object[] contents = ((List) data).toArray();
            int diff = i;
            ensureBuffers(contents.length * 4);
            for (Object o : contents) {
              diff = appendone(sigb, i, o);
            }
            i = diff;
          } else if (data instanceof Map) {
            int diff = i;
            ensureBuffers(((Map) data).size() * 6);
            for (Map.Entry<Object, Object> o : ((Map<Object, Object>) data).entrySet()) {
              diff = appendone(sigb, i, o);
            }
            if (i == diff) {
              // advance the type parser even on 0-size arrays.
              Vector<Type> temp = new Vector<Type>();
              byte[] temp2 = new byte[sigb.length - diff];
              System.arraycopy(sigb, diff, temp2, 0, temp2.length);
              String temp3 = new String(temp2);
              int temp4 = Marshalling.getJavaType(temp3, temp, 1);
              diff += temp4;
            }
            i = diff;
          } else {
            Object[] contents = (Object[]) data;
            ensureBuffers(contents.length * 4);
            int diff = i;
            for (Object o : contents) {
              diff = appendone(sigb, i, o);
            }
            i = diff;
          }
          marshallint(bytecounter - c, alen, 0, 4);
          break;
        case ArgumentType.STRUCT1:
          // Structs are aligned to 8 bytes
          // and simply contain each element marshalled in order
          Object[] contents;
          if (data instanceof Container) {
            contents = ((Container) data).getParameters();
          } else {
            contents = (Object[]) data;
          }
          ensureBuffers(contents.length * 4);
          int j = 0;
          for (i++; sigb[i] != ArgumentType.STRUCT2; i++) {
            i = appendone(sigb, i, contents[j++]);
          }
          break;
        case ArgumentType.DICT_ENTRY1:
          // Dict entries are the same as structs.
          if (data instanceof Map.Entry) {
            i++;
            i = appendone(sigb, i, ((Map.Entry) data).getKey());
            i++;
            i = appendone(sigb, i, ((Map.Entry) data).getValue());
            i++;
          } else {
            contents = (Object[]) data;
            j = 0;
            for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++) {
              i = appendone(sigb, i, contents[j++]);
            }
          }
          break;
        case ArgumentType.VARIANT:
          // Variants are marshalled as a signature
          // followed by the value.
          if (data instanceof Variant) {
            Variant var = (Variant) data;
            appendone(new byte[] { ArgumentType.SIGNATURE }, 0, var.getSig());
            appendone((var.getSig()).getBytes(), 0, var.getValue());
          } else if (data instanceof Object[]) {
            contents = (Object[]) data;
            appendone(new byte[] { ArgumentType.SIGNATURE }, 0, contents[0]);
            appendone(((String) contents[0]).getBytes(), 0, contents[1]);
          } else {
            String sig = Marshalling.getDBusType(data.getClass())[0];
            appendone(new byte[] { ArgumentType.SIGNATURE }, 0, sig);
            appendone((sig).getBytes(), 0, data);
          }
          break;
      }
      return i;
    } catch (ClassCastException CCe) {
      throw new MarshallingException(MessageFormat.format(_("Trying to marshall to unconvertable type (from {0} to {1})."),
          new Object[] { data.getClass().getName(), sigb[sigofs] }));
    }
  }

  /**
   * Demarshalls an integer of a given width from a buffer. Endianness is
   * determined from the format of the message.
   * 
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          The offset to demarshall from.
   * @param width
   *          The byte-width of the int.
   */
  public long demarshallint(byte[] buf, int ofs, int width) {
    return big ? demarshallintBig(buf, ofs, width) : demarshallintLittle(buf, ofs, width);
  }

  /**
   * Ensures there are enough free buffers.
   * 
   * @param num
   *          number of free buffers to create.
   */
  private void ensureBuffers(int num) {
    int increase = num - wiredata.length + bufferuse;
    if (increase > 0) {
      if (increase < BUFFERINCREMENT) {
        increase = BUFFERINCREMENT;
      }
      byte[][] temp = new byte[wiredata.length + increase][];
      System.arraycopy(wiredata, 0, temp, 0, wiredata.length);
      wiredata = temp;
    }
  }

  /**
   * Demarshall values from a buffer.
   * 
   * @param sig
   *          The D-Bus signature(s) of the value(s).
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          The offset into the data buffer to start.
   * @return The demarshalled value(s).
   */
  public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException {
    return extract(sig, buf, new int[] { 0, ofs });
  }

  /**
   * Demarshall values from a buffer.
   * 
   * @param sig
   *          The D-Bus signature(s) of the value(s).
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          An array of two ints, the offset into the signature and the offset
   *          into the data buffer. These values will be updated to the start of
   *          the next value ofter demarshalling.
   * @return The demarshalled value(s).
   */
  public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException {
    Vector<Object> rv = new Vector<Object>();
    byte[] sigb = sig.getBytes();
    for (int[] i = ofs; i[0] < sigb.length; i[0]++) {
      rv.add(extractone(sigb, buf, i, false));
    }
    return rv.toArray();
  }

  /**
   * Demarshall one value from a buffer.
   * 
   * @param sigb
   *          A buffer of the D-Bus signature.
   * @param buf
   *          The buffer to demarshall from.
   * @param ofs
   *          An array of two ints, the offset into the signature buffer and the
   *          offset into the data buffer. These values will be updated to the
   *          start of the next value ofter demarshalling.
   * @param contained
   *          converts nested arrays to Lists
   * @return The demarshalled value.
   */
  @SuppressWarnings("unchecked")
  private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException {
    Object rv = null;
    ofs[1] = align(ofs[1], sigb[ofs[0]]);
    switch (sigb[ofs[0]]) {
      case ArgumentType.BYTE:
        rv = buf[ofs[1]++];
        break;
      case ArgumentType.UINT32:
        rv = new UInt32(demarshallint(buf, ofs[1], 4));
        ofs[1] += 4;
        break;
      case ArgumentType.INT32:
        rv = (int) demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        break;
      case ArgumentType.INT16:
        rv = (short) demarshallint(buf, ofs[1], 2);
        ofs[1] += 2;
        break;
      case ArgumentType.UINT16:
        rv = new UInt16((int) demarshallint(buf, ofs[1], 2));
        ofs[1] += 2;
        break;
      case ArgumentType.INT64:
        rv = demarshallint(buf, ofs[1], 8);
        ofs[1] += 8;
        break;
      case ArgumentType.UINT64:
        long top;
        long bottom;
        if (big) {
          top = demarshallint(buf, ofs[1], 4);
          ofs[1] += 4;
          bottom = demarshallint(buf, ofs[1], 4);
        } else {
          bottom = demarshallint(buf, ofs[1], 4);
          ofs[1] += 4;
          top = demarshallint(buf, ofs[1], 4);
        }
        rv = new UInt64(top, bottom);
        ofs[1] += 4;
        break;
      case ArgumentType.DOUBLE:
        long l = demarshallint(buf, ofs[1], 8);
        ofs[1] += 8;
        rv = Double.longBitsToDouble(l);
        break;
      case ArgumentType.FLOAT:
        int rf = (int) demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        rv = Float.intBitsToFloat(rf);
        break;
      case ArgumentType.BOOLEAN:
        rf = (int) demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        rv = (1 == rf) ? Boolean.TRUE : Boolean.FALSE;
        break;
      case ArgumentType.ARRAY:
        long size = demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        byte algn = (byte) getAlignment(sigb[++ofs[0]]);
        ofs[1] = align(ofs[1], sigb[ofs[0]]);
        int length = (int) (size / algn);
        if (length > AbstractConnection.MAX_ARRAY_LENGTH) { throw new MarshallingException(_("Arrays must not exceed ")
            + AbstractConnection.MAX_ARRAY_LENGTH); }
        // optimise primatives
        switch (sigb[ofs[0]]) {
          case ArgumentType.BYTE:
            rv = new byte[length];
            System.arraycopy(buf, ofs[1], rv, 0, length);
            ofs[1] += size;
            break;
          case ArgumentType.INT16:
            rv = new short[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn);
            }
            break;
          case ArgumentType.INT32:
            rv = new int[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn);
            }
            break;
          case ArgumentType.INT64:
            rv = new long[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn);
            }
            break;
          case ArgumentType.BOOLEAN:
            rv = new boolean[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn));
            }
            break;
          case ArgumentType.FLOAT:
            rv = new float[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((float[]) rv)[j] = Float.intBitsToFloat((int) demarshallint(buf, ofs[1], algn));
            }
            break;
          case ArgumentType.DOUBLE:
            rv = new double[length];
            for (int j = 0; j < length; j++, ofs[1] += algn) {
              ((double[]) rv)[j] = Double.longBitsToDouble(demarshallint(buf, ofs[1], algn));
            }
            break;
          case ArgumentType.DICT_ENTRY1:
            if (0 == size) {
              // advance the type parser even on 0-size arrays.
              Vector<Type> temp = new Vector<Type>();
              byte[] temp2 = new byte[sigb.length - ofs[0]];
              System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
              String temp3 = new String(temp2);
              // ofs[0] gets incremented anyway. Leave one character on the
              // stack
              int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
              ofs[0] += temp4;
            }
            int ofssave = ofs[0];
            long end = ofs[1] + size;
            Vector<Object[]> entries = new Vector<Object[]>();
            while (ofs[1] < end) {
              ofs[0] = ofssave;
              entries.add((Object[]) extractone(sigb, buf, ofs, true));
            }
            rv = new DBusMap<Object, Object>(entries.toArray(new Object[0][]));
            break;
          default:
            if (0 == size) {
              // advance the type parser even on 0-size arrays.
              Vector<Type> temp = new Vector<Type>();
              byte[] temp2 = new byte[sigb.length - ofs[0]];
              System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length);
              String temp3 = new String(temp2);
              // ofs[0] gets incremented anyway. Leave one character on the
              // stack
              int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1;
              ofs[0] += temp4;
            }
            ofssave = ofs[0];
            end = ofs[1] + size;
            Vector<Object> contents = new Vector<Object>();
            while (ofs[1] < end) {
              ofs[0] = ofssave;
              contents.add(extractone(sigb, buf, ofs, true));
            }
            rv = contents;
        }
        if (contained && !(rv instanceof List) && !(rv instanceof Map)) {
          rv = ArrayFrob.listify(rv);
        }
        break;
      case ArgumentType.STRUCT1:
        Vector<Object> contents = new Vector<Object>();
        while (sigb[++ofs[0]] != ArgumentType.STRUCT2) {
          contents.add(extractone(sigb, buf, ofs, true));
        }
        rv = contents.toArray();
        break;
      case ArgumentType.DICT_ENTRY1:
        Object[] decontents = new Object[2];
        ofs[0]++;
        decontents[0] = extractone(sigb, buf, ofs, true);
        ofs[0]++;
        decontents[1] = extractone(sigb, buf, ofs, true);
        ofs[0]++;
        rv = decontents;
        break;
      case ArgumentType.VARIANT:
        int[] newofs = new int[] { 0, ofs[1] };
        String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0];
        newofs[0] = 0;
        rv = new Variant<Object>(extract(sig, buf, newofs)[0], sig);
        ofs[1] = newofs[1];
        break;
      case ArgumentType.STRING:
        length = (int) demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        try {
          rv = new String(buf, ofs[1], length, "UTF-8");
        } catch (UnsupportedEncodingException UEe) {
          throw new DBusException(_("System does not support UTF-8 encoding"));
        }
        ofs[1] += length + 1;
        break;
      case ArgumentType.OBJECT_PATH:
        length = (int) demarshallint(buf, ofs[1], 4);
        ofs[1] += 4;
        rv = new ObjectPath(getSource(), new String(buf, ofs[1], length));
        ofs[1] += length + 1;
        break;
      case ArgumentType.SIGNATURE:
        length = (buf[ofs[1]++] & 0xFF);
        rv = new String(buf, ofs[1], length);
        ofs[1] += length + 1;
        break;
      default:
        throw new UnknownTypeCodeException(sigb[ofs[0]]);
    }
    return rv;
  }

  /**
   * Returns the destination of the message.
   */
  public String getDestination() {
    return (String) headers.get(HeaderField.DESTINATION);
  }

  /**
   * Returns the message flags.
   */
  public int getFlags() {
    return flags;
  }

  /**
   * Returns the value of the header field of a given field.
   * 
   * @param type
   *          The field to return.
   * @return The value of the field or null if unset.
   */
  public Object getHeader(byte type) {
    return headers.get(type);
  }

  /**
   * Returns the interface of the message.
   */
  public String getInterface() {
    return (String) headers.get(HeaderField.INTERFACE);
  }

  /**
   * Returns the member name or error name this message represents.
   */
  public String getName() {
    if (this instanceof Error) {
      return (String) headers.get(HeaderField.ERROR_NAME);
    } else {
      return (String) headers.get(HeaderField.MEMBER);
    }
  }

  /**
   * Parses and returns the parameters to this message as an Object array.
   */
  public Object[] getParameters() throws DBusException {
    if ((null == args) && (null != body)) {
      String sig = (String) headers.get(HeaderField.SIGNATURE);
      if ((null != sig) && (0 != body.length)) {
        args = extract(sig, body, 0);
      } else {
        args = new Object[0];
      }
    }
    return args;
  }

  /**
   * Returns the object path of the message.
   */
  public String getPath() {
    Object o = headers.get(HeaderField.PATH);
    if (null == o) { return null; }
    return o.toString();
  }

  /**
   * If this is a reply to a message, this returns its serial.
   * 
   * @return The reply serial, or 0 if it is not a reply.
   */
  public long getReplySerial() {
    Number l = (Number) headers.get(HeaderField.REPLY_SERIAL);
    if (null == l) { return 0; }
    return l.longValue();
  }

  /**
   * Returns the message serial ID (unique for this connection)
   * 
   * @return the message serial.
   */
  public long getSerial() {
    return serial;
  }

  /**
   * Returns the dbus signature of the parameters.
   */
  public String getSig() {
    return (String) headers.get(HeaderField.SIGNATURE);
  }

  /**
   * Returns the Bus ID that sent the message.
   */
  public String getSource() {
    return (String) headers.get(HeaderField.SENDER);
  }

  public byte[][] getWireData() {
    return wiredata;
  }

  /**
   * Marshalls an integer of a given width into a buffer. Endianness is
   * determined from the message.
   * 
   * @param l
   *          The integer to marshall.
   * @param buf
   *          The buffer to marshall to.
   * @param ofs
   *          The offset to marshall to.
   * @param width
   *          The byte-width of the int.
   */
  public void marshallint(long l, byte[] buf, int ofs, int width) {
    if (big) {
      marshallintBig(l, buf, ofs, width);
    } else {
      marshallintLittle(l, buf, ofs, width);
    }
  }

  /**
   * Pad the message to the proper alignment for the given type.
   */
  public void pad(byte type) {
    int a = getAlignment(type);
    int b = (int) ((bytecounter - preallocated) % a);
    if (0 == b) { return; }
    a = (a - b);
    if (preallocated > 0) {
      paofs += a;
      preallocated -= a;
    } else {
      appendBytes(padding[a]);
    }
  }

  /**
   * Create a message from wire-format data.
   * 
   * @param msg
   *          D-Bus serialized data of type yyyuu
   * @param headers
   *          D-Bus serialized data of type a(yv)
   * @param body
   *          D-Bus serialized data of the signature defined in headers.
   */
  @SuppressWarnings("unchecked")
  void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException {
    big = (msg[0] == Endian.BIG);
    type = msg[1];
    flags = msg[2];
    protover = msg[3];
    wiredata[0] = msg;
    wiredata[1] = headers;
    wiredata[2] = body;
    this.body = body;
    bufferuse = 3;
    bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue();
    serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue();
    bytecounter = msg.length + headers.length + body.length;
    Object[] hs = extract("a(yv)", headers, 0);
    for (Object o : (Vector<Object>) hs[0]) {
      this.headers.put((Byte) ((Object[]) o)[0], ((Variant<Object>) ((Object[]) o)[1]).getValue());
    }
  }

  /**
   * Create a buffer of num bytes. Data is copied to this rather than added to
   * the buffer list.
   */
  private void preallocate(int num) {
    preallocated = 0;
    pabuf = new byte[num];
    appendBytes(pabuf);
    preallocated = num;
    paofs = 0;
  }

  protected void setArgs(Object[] args) {
    this.args = args;
  }

  /**
   * Warning, do not use this method unless you really know what you are doing.
   */
  public void setSource(String source) throws DBusException {
    if (null != body) {
      wiredata = new byte[BUFFERINCREMENT][];
      bufferuse = 0;
      bytecounter = 0;
      preallocate(12);
      append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial);
      headers.put(HeaderField.SENDER, source);
      Object[][] newhead = new Object[headers.size()][];
      int i = 0;
      for (Byte b : headers.keySet()) {
        newhead[i] = new Object[2];
        newhead[i][0] = b;
        newhead[i][1] = headers.get(b);
        i++;
      }
      append("a(yv)", (Object) newhead);
      pad((byte) 8);
      appendBytes(body);
    }
  }

  /**
   * Formats the message in a human-readable format.
   */
  @Override
  public String toString() {
    StringBuffer sb = new StringBuffer();
    sb.append(getClass().getSimpleName());
    sb.append('(');
    sb.append(flags);
    sb.append(',');
    sb.append(serial);
    sb.append(')');
    sb.append(' ');
    sb.append('{');
    sb.append(' ');
    if (headers.size() == 0) {
      sb.append('}');
    } else {
      for (Byte field : headers.keySet()) {
        sb.append(getHeaderFieldName(field));
        sb.append('=');
        sb.append('>');
        sb.append(headers.get(field).toString());
        sb.append(',');
        sb.append(' ');
      }
      sb.setCharAt(sb.length() - 2, ' ');
      sb.setCharAt(sb.length() - 1, '}');
    }
    sb.append(' ');
    sb.append('{');
    sb.append(' ');
    Object[] args = null;
    try {
      args = getParameters();
    } catch (DBusException DBe) {
    }
    if ((null == args) || (0 == args.length)) {
      sb.append('}');
    } else {
      for (Object o : args) {
        if (o instanceof Object[]) {
          sb.append(Arrays.deepToString((Object[]) o));
        } else if (o instanceof byte[]) {
          sb.append(Arrays.toString((byte[]) o));
        } else if (o instanceof int[]) {
          sb.append(Arrays.toString((int[]) o));
        } else if (o instanceof short[]) {
          sb.append(Arrays.toString((short[]) o));
        } else if (o instanceof long[]) {
          sb.append(Arrays.toString((long[]) o));
        } else if (o instanceof boolean[]) {
          sb.append(Arrays.toString((boolean[]) o));
        } else if (o instanceof double[]) {
          sb.append(Arrays.toString((double[]) o));
        } else if (o instanceof float[]) {
          sb.append(Arrays.toString((float[]) o));
        } else {
          sb.append(o.toString());
        }
        sb.append(',');
        sb.append(' ');
      }
      sb.setCharAt(sb.length() - 2, ' ');
      sb.setCharAt(sb.length() - 1, '}');
    }
    return sb.toString();
  }
}
